import ExcelJS from 'exceljs';
import * as fs from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import { TEMPLATES_DIR } from '../config';
import { nanoid } from '../utils/idgen';
import { createErrorLogContext, logger } from '../utils/logger.js';

export class TemplateManager {
  async copyTemplate(templateName: string, name: string): Promise<string> {
    // Handle both absolute and relative template paths
    const templateRealPath = path.isAbsolute(templateName)
      ? templateName
      : path.join(TEMPLATES_DIR, templateName);
    const tempPath = path.join(
      os.tmpdir(),
      nanoid(),
      `${path.basename(name, path.extname(name))}${path.extname(templateRealPath)}`
    );
    await fs.mkdir(path.dirname(tempPath), { recursive: true });

    try {
      // Check if template exists
      try {
        await fs.access(templateRealPath, fs.constants.F_OK);
      } catch (err) {
        logger.error(
          'Template Manager: Template file not found',
          createErrorLogContext(err, {
            templateRealPath,
            templateName,
          })
        );
        throw new Error(`Template file not found: ${templateRealPath}`);
      }

      // Copy template to temporary location
      const templateContent = await fs.readFile(templateRealPath);
      await fs.writeFile(tempPath, templateContent);
      return tempPath;
    } catch (error) {
      logger.error(
        'Template Manager: Failed to copy template',
        createErrorLogContext(error, {
          templateName,
          templateRealPath,
          tempPath,
        })
      );
      throw new Error(
        `Failed to copy template: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  async findFieldPositions(
    filePath: string,
    fields: string[]
  ): Promise<{ headerRow: number; fieldPositions: number[] }> {
    try {
      // Check if file exists and is readable
      await fs.access(filePath, fs.constants.R_OK);

      const workbook = new ExcelJS.Workbook();
      await workbook.xlsx.readFile(filePath);

      if (!workbook.worksheets || workbook.worksheets.length === 0) {
        throw new Error('Invalid or corrupted Excel file: no sheets found');
      }

      const worksheet = workbook.worksheets[0];
      if (!worksheet) {
        throw new Error('No worksheet found in workbook');
      }

      const positions: Record<string, number> = {};
      let headerRow = -1;
      const availableHeaders: string[] = [];

      // Scan first 5 rows (1-indexed in ExcelJS)
      for (let row = 1; row <= 5; row++) {
        // Scan columns in this row (up to column Z)
        for (let col = 1; col <= 30; col++) {
          const cell = worksheet.getCell(row, col);
          if (cell.value && typeof cell.value === 'string') {
            const cellValue = cell.value.trim();
            
            // Collect all available headers for error reporting
            if (cellValue && !availableHeaders.includes(cellValue)) {
              availableHeaders.push(cellValue);
            }

            // Check if this cell value matches any of the requested fields
            if (fields.includes(cellValue)) {
              positions[cellValue] = col; // ExcelJS uses 1-based indexing
            }
          }
          // 一列中已经找到所有的下标了则不需要继续找
          if (Object.keys(positions).length === fields.length) {
            break;
          }
        }

        // If we found all requested fields in this row, use this as header row
        if (Object.keys(positions).length === fields.length) {
          headerRow = row;
          break;
        }
      }

      if (headerRow === -1) {
        const missingFields = fields.filter(field => !positions[field]);
        throw new Error(
          'No header row found in the first 5 rows. Missing fields: ' +
            missingFields.join(', ') +
            '. Available headers in template: ' +
            availableHeaders.join(', ')
        );
      }
      const result: number[] = fields.map(it => positions[it]!);

      return { headerRow, fieldPositions: result };
    } catch (error) {
      logger.error(
        'Template Manager: Failed to find field positions',
        createErrorLogContext(error, {
          filePath,
          fields,
        })
      );
      throw new Error(
        'Failed to find field positions: ' +
          (error instanceof Error ? error.message : 'Unknown error')
      );
    }
  }

  async writeToTemplate(
    filePath: string,
    data: Array<string[]>,
    fieldPositions: number[],
    headerRow: number,
    nextRow: number
  ): Promise<void> {
    try {
      // Check if file exists and is readable
      await fs.access(filePath, fs.constants.R_OK | fs.constants.W_OK);

      const workbook = new ExcelJS.Workbook();
      await workbook.xlsx.readFile(filePath);

      if (!workbook.worksheets || workbook.worksheets.length === 0) {
        throw new Error('Invalid or corrupted Excel file: no sheets found');
      }

      const worksheet = workbook.worksheets[0];
      if (!worksheet) {
        throw new Error('No worksheet found in workbook');
      }

      // Check if there's an existing sample row (first data row) for style reference
      const sampleRowIndex = headerRow + 1;
      const sampleRowStyles: Record<number, any> = {};

      Object.values(fieldPositions).forEach(col => {
        const sampleCell = worksheet.getCell(sampleRowIndex, col);
        if (sampleCell.value !== null) {
          // If there's existing data
          sampleRowStyles[col] = {
            font: sampleCell.font ? { ...sampleCell.font } : undefined,
            alignment: sampleCell.alignment ? { ...sampleCell.alignment } : undefined,
            border: sampleCell.border ? { ...sampleCell.border } : undefined,
            fill: sampleCell.fill ? { ...sampleCell.fill } : undefined,
            numFmt: sampleCell.numFmt,
            protection: sampleCell.protection ? { ...sampleCell.protection } : undefined,
          };
        }
      });

      for (let i = 0; i < data.length; i++) {
        const rowData = data[i]!;
        const rowNumber = nextRow + i;

        for (let j = 0; j < fieldPositions.length; j++) {
          const col = fieldPositions[j]!;
          const value = rowData[j];
          if (!value) continue;

          const cell = worksheet.getCell(rowNumber, col);

          // Set the value
          cell.value = value;

          // Apply styles with priority: existing cell > sample row > header row > default
          const existingStyle = cell.font || cell.alignment || cell.border || cell.fill;
          const sampleStyle = sampleRowStyles[col];

          if (!existingStyle) {
            // Choose style source
            const styleSource = sampleStyle;

            if (styleSource) {
              // Apply comprehensive style
              if (styleSource.font) {
                cell.font = styleSource.font;
              }

              if (styleSource.alignment) {
                cell.alignment = styleSource.alignment;
              } else {
                // Default alignment for data cells
                cell.alignment = {
                  wrapText: true,
                  vertical: 'top',
                  horizontal: 'left',
                };
              }

              if (styleSource.border) {
                cell.border = styleSource.border;
              } else {
                // Default border for data cells
                cell.border = {
                  top: { style: 'thin' },
                  left: { style: 'thin' },
                  bottom: { style: 'thin' },
                  right: { style: 'thin' },
                };
              }

              if (styleSource.fill) {
                cell.fill = styleSource.fill;
              }

              if (styleSource.numFmt) {
                cell.numFmt = styleSource.numFmt;
              }
            } else {
              // Apply default style when no style source is available
              cell.alignment = {
                wrapText: true,
                vertical: 'top',
                horizontal: 'left',
              };
              cell.border = {
                top: { style: 'thin' },
                left: { style: 'thin' },
                bottom: { style: 'thin' },
                right: { style: 'thin' },
              };
            }
          }
        }
      }

      // Save the file
      await workbook.xlsx.writeFile(filePath);
    } catch (error) {
      logger.error(
        'Template Manager: Failed to write to template',
        createErrorLogContext(error, {
          filePath,
          dataRowsCount: data.length,
          fieldPositions,
          headerRow,
        })
      );
      throw new Error(
        'Failed to write to template: ' + (error instanceof Error ? error.message : 'Unknown error')
      );
    }
  }
}
